﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using Co9320.NamaAlert.Engine.Feeder.Util;
using Model=Co9320.NamaAlert.Model;
using Feeder = Co9320.NamaAlert.Engine.Feeder;
using System.Diagnostics;
using Co9320.NamaAlert.Engine.DebugHelper;


namespace Co9320.NamaAlert.Engine
{
	internal sealed class ReserveNotification
	{
		private static readonly TimeSpan AlertBeforeThresHold = new TimeSpan(0, 15, 0);
		private static readonly TimeSpan DefaultAlertBefore = new TimeSpan(0, 0, 10);
		private static readonly TimeSpan LeadTime = new TimeSpan(0, 5, 0);
		private static readonly TimeSpan ChangeLimit = new TimeSpan(0, 0, 30);

		private Queue<Feeder::LiveProgramSet> Queue_;

		private Timer FeedTimer_;
		private Timer RaiseTimer_;
		
		public event EventHandler<Model::LiveProgramEventArgs> Noticed;
		public event EventHandler<Model::LiveProgramEventArgs> UpDate;

		private TimeSpan SwingSpan_;
		private LiveNotification Parent_;
		private TimeSpan AlertBefore_;
		private readonly object AlertBeforeSync_ = new object();

		public ReserveNotification(TimeSpan AlertBefore,LiveNotification Parent)
		{
			Debug.Assert(Parent != null);
			Parent_ = Parent;
			
			if (AlertBefore.Ticks < 0 || AlertBefore > AlertBeforeThresHold)
			{
				this.AlertBefore_ = DefaultAlertBefore;
			}
			else
			{
				this.AlertBefore_ = AlertBefore;
			}

			Queue_ = new Queue<Feeder::LiveProgramSet>();
			FeedTimer_ = new Timer(new TimerCallback(FeedingWork), null, Timeout.Infinite, Timeout.Infinite);
			RaiseTimer_ = new Timer(new TimerCallback(RaisingWork), null, Timeout.Infinite, Timeout.Infinite);

			DateTime TargetFrame;


			try
			{
				var List = Feeder::ReserveDataFeeder.GetCurrentReserve(out TargetFrame);

				foreach (var elem in List)
				{
					Queue_.Enqueue(elem);
				}

			}
			catch(Exception ex)
			{
				Parent.InvokeSendSystemMessage(new Co9320.NamaAlert.Model.SystemMessageEventArgs(
					 Co9320.NamaAlert.Model.SystemMessageKind.Error, "Can't feed ReserveProgram"));

				DebugHelper.LogOutputter.WriteElement(LogMode.Caution, ex);

				TargetFrame = DateTime.Now.Round();
			}


			RaiseTimer_.Change(0, Timeout.Infinite);

			SetSwingSpan();

			SetFeedTimer(TargetFrame);
			SetRaiseTimer(TargetFrame);
		}

		private void SetSwingSpan()
		{
			long seed = DateTime.Now.ToUnixTime();

			seed %=59;
			SwingSpan_ = new TimeSpan(0, 0, (int)seed);
		}

		private void SetFeedTimer(DateTime CurrentFrameTime)
		{
			DateTime TargetFrame = CurrentFrameTime.NextFrameStart();

			TargetFrame -= this.SwingSpan_;
			TargetFrame -= this.AlertBefore;
			TargetFrame -= LeadTime;

			TimeSpan Interval = TargetFrame - DateTime.Now;

			if (Interval.Ticks > 0)
			{
				FeedTimer_.Change(Interval, new TimeSpan(0, 0, 0, 0, -1));
			}
			else
			{
				FeedTimer_.Change(0, Timeout.Infinite);
			}
		}


		private void SetRaiseTimer(DateTime CurrentFrameTime)
		{
			DateTime TargetFrame = CurrentFrameTime.NextFrameStart();

			TargetFrame -= this.AlertBefore;

			TimeSpan Interval = TargetFrame - DateTime.Now;

			if (Interval.Ticks > 0)
			{
				RaiseTimer_.Change(Interval, new TimeSpan(0, 0, 0, 0, -1));
			}
			else
			{
				RaiseTimer_.Change(0, Timeout.Infinite);
			}
		}

		private void FeedingWork(object Dmmy)
		{
			DateTime tmp;
			try
			{
				lock (Queue_)
				{
					try
					{
						var lst = Feeder::ReserveDataFeeder.GetNextReserve(out tmp);

						foreach (var elem in lst)
						{
							Queue_.Enqueue(elem);
						}
					}
					catch (System.Net.WebException)
					{
						Parent_.InvokeSendSystemMessage(new Co9320.NamaAlert.Model.SystemMessageEventArgs(Co9320.NamaAlert.Model.SystemMessageKind.Error,
							"Can't Feed reserve program."));

						throw;
					}
				}
			}
			catch(Exception ex)
			{
				DebugHelper.LogOutputter.WriteElement(LogMode.Caution, ex);
				tmp = DateTime.Now.Round();
			}

			SetFeedTimer(tmp);
		}

		private void RaisingWork(object Dmmy)
		{
			List<StateChangedEventArgs> lst = new List<StateChangedEventArgs>();

			lock (Queue_)
			{
				while (Queue_.Count != 0)
				{
					var elem = Queue_.Dequeue();

					switch (elem.Status)
					{
						case Feeder::CurrentStatus.Closed:
							break;

						case Feeder::CurrentStatus.CommingSoon:
							lst.Add(new StateChangedEventArgs(InspectionResult.DetectOnAir, elem.Program));
							break;

						case Feeder::CurrentStatus.OnAir:
							lst.Add(new StateChangedEventArgs(InspectionResult.Exists, elem.Program));
							break;

						default:
							throw new Exception();
					}
				}
			}

			Parent_.SendStateChanged(lst);
		}
		

		private void OnNoticed(object sender, Model::LiveProgramEventArgs e)
		{
			if (Noticed != null)
			{
				Noticed(sender, e);
			}
		}

		private void OnUpDate(object sender, Model::LiveProgramEventArgs e)
		{
			if (UpDate != null)
			{
				UpDate(sender, e);
			}
		}

		public TimeSpan AlertBefore
		{
			get
			{
				lock (AlertBeforeSync_)
				{
					return AlertBefore_;
				}
			}
			set
			{
				if (value > AlertBeforeThresHold || value < new TimeSpan())
				{
					throw new ArgumentOutOfRangeException("AlertBefore");
				}

				DateTime Limit0=DateTime.Now.NextFrameStart();
				Limit0 -= AlertBefore_;
				Limit0 -= SwingSpan_;
				Limit0 -= ChangeLimit;
				Limit0 -= LeadTime;

				AlertBefore_ = value;

				DateTime Limit1 = DateTime.Now.NextFrameStart();
				Limit1 -= AlertBefore_;
				Limit1 -= SwingSpan_;
				Limit1 -= ChangeLimit;
				Limit1 -= LeadTime;
				

				if (Limit0 > DateTime.Now && Limit1>DateTime.Now)
				{
					SetFeedTimer(DateTime.Now);
					SetRaiseTimer(DateTime.Now);
				}
			}
		}
	}
}

